﻿using AnimCollections;
using AOT;
using Unity.Burst;
using UnityEngine.Animations;
using UnityEngine.Playables;

namespace DA.AnimGraph {
    
    [BurstCompile]
    public unsafe partial struct AxisBlend1DNode : INode {

        public struct SetupData : INodeSetupData {
            public FloatLinkDesc inputDesc;
            public int[] portIds; // 长度和clipsPos一样，内容是输入端口的序号，将pos映射到输入端口序号
            public float[] portPos; // 要求已经从小到大排好
        }

        //
        private FloatLink linkInput;
        private NativeSimpleArray<int> inputIds;
        private NativeSimpleArray<float> portPos;

        //
        private NodeHead head;
        private AnimationMixerPlayable playable;
        private int lastIndex; // 记录上次的结果在 [lastIndex, lastIndex+1) 范围内

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.DisposeDelegate))]
        private static bool Dispose(in void* node) {
            var self = (AxisBlend1DNode*)node;

            self->inputIds.Dispose();
            self->portPos.Dispose();

            return true;
        }

        private void Setup(in SetupData data, ref GraphBuffer buffer, in NodeLink* link) {
            linkInput = new FloatLink(data.inputDesc);
            buffer.MallocNativeSimpleArray(out inputIds, data.portIds);
            buffer.MallocNativeSimpleArray(out portPos, data.portPos);
        }

        private static Playable BuildPlayableGraph(in void* node, in NodeLink* link, in PlayableGraph graph, in BuildContext buildContext) {
            var self = (AxisBlend1DNode*)node;

            self->head = new NodeHead(link);

            var childrenCount = self->head.childrenCount;
            self->playable = AnimationMixerPlayable.Create(graph, childrenCount);
            for (int i = 0; i < childrenCount; i++) {
                self->playable.ConnectInput(i, self->head.children[i]->BuildPlayableGraph(graph, buildContext), 0);
            }

            return self->playable;
        }

        [BurstCompile]
        [MonoPInvokeCallback(typeof(NodeLink.EvaluateDelegate))]
        private static void Evaluate(in void* node, bool onEnter, in EvaluateContext evaluateContext, in GraphContext graphContext, ref UpstreamData upstream, ref ScopeContext scope) {
            var self = (AxisBlend1DNode*)node;

            var childrenCount = self->head.childrenCount;
            if (childrenCount == 0) return;
            if (childrenCount == 1) {
                self->head.weights[0] = 1;
                var childScope = new ScopeContext(scope, 1);
                NodeLink.Evaluate(self->head.children[0], evaluateContext, graphContext, ref upstream, ref childScope);
                scope.Fusion(childScope);
                return;
            }

            float inputPos = self->linkInput.Evaluate(onEnter, evaluateContext, graphContext, scope);
            self->EvaluateWeight(inputPos);
            self->NormalizeWeights();

            for (int i = 0; i < childrenCount; i++) {
                var w = self->head.weights[i];
                if (w > 0) {
                    var childScope = new ScopeContext(scope, w);
                    NodeLink.Evaluate(self->head.children[i], evaluateContext, graphContext, ref upstream, ref childScope);
                    scope.Fusion(childScope);
                }
            }
        }

        private void EvaluateWeight(float inputPos) {
            var posCount = portPos.Length;
            if (lastIndex < 0 || lastIndex > posCount) {
                lastIndex = posCount / 2;
            }

            for (int i = 0; i < posCount; i++) {
                float curPos = portPos[lastIndex];

                if (curPos.IsNear(inputPos)) {
                    head.weights[inputIds[lastIndex]] = 1;
                    return;
                }

                if (inputPos > curPos) {
                    if (lastIndex == posCount - 1) {
                        // 输入比最大的还大
                        head.weights[inputIds[lastIndex]] = 1;
                        return;
                    } else if (inputPos < portPos[lastIndex + 1]) {
                        // 在两者之间
                        float min = portPos[lastIndex];
                        float max = portPos[lastIndex + 1];
                        float w = (inputPos - min) / (max - min);
                        head.weights[inputIds[lastIndex]] += 1 - w;
                        head.weights[inputIds[lastIndex + 1]] += w;
                        return;
                    } else {
                        lastIndex += 1;
                    }
                } else {
                    if (lastIndex == 0) {
                        // 输入比最小的还小
                        head.weights[inputIds[0]] = 1;
                        return;
                    } else if (inputPos > portPos[lastIndex - 1]) {
                        // 在两者之间
                        lastIndex -= 1; // 让lastIndex成为较小的那个

                        float min = portPos[lastIndex];
                        float max = portPos[lastIndex + 1];
                        float w = (inputPos - min) / (max - min);
                        head.weights[inputIds[lastIndex]] += 1 - w;
                        head.weights[inputIds[lastIndex + 1]] += w;
                        return;
                    } else {
                        lastIndex -= 1;
                    }
                }
            }
        }
        private void NormalizeWeights() {
            float w = 0;
            for (int i = 0; i < head.childrenCount; i++) {
                w += head.weights[i];
            }
            if (w == 0) return;
            if (w.IsNear(1)) return;
            w = 1 / w;
            for (int i = 0; i < head.childrenCount; i++) {
                head.weights[i] *= w;
            }
        }

    }
}
